home *** CD-ROM | disk | FTP | other *** search
/ Freelog 125 / Freelog_MarsAvril2015_No125.iso / ViePratique / gnucash / gnucash-2.6.5-setup.exe / {app} / bin / intltool-merge~ < prev    next >
Text File  |  2008-09-23  |  40KB  |  1,549 lines

  1. #!/opt/perl/bin/perl -w
  2. # -*- Mode: perl; indent-tabs-mode: nil; c-basic-offset: 4  -*-
  3.  
  4. #
  5. #  The Intltool Message Merger
  6. #
  7. #  Copyright (C) 2000, 2003 Free Software Foundation.
  8. #  Copyright (C) 2000, 2001 Eazel, Inc
  9. #
  10. #  Intltool is free software; you can redistribute it and/or
  11. #  modify it under the terms of the GNU General Public License 
  12. #  version 2 published by the Free Software Foundation.
  13. #
  14. #  Intltool is distributed in the hope that it will be useful,
  15. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17. #  General Public License for more details.
  18. #
  19. #  You should have received a copy of the GNU General Public License
  20. #  along with this program; if not, write to the Free Software
  21. #  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  22. #
  23. #  As a special exception to the GNU General Public License, if you
  24. #  distribute this file as part of a program that contains a
  25. #  configuration script generated by Autoconf, you may include it under
  26. #  the same distribution terms that you use for the rest of that program.
  27. #
  28. #  Authors:  Maciej Stachowiak <mjs@noisehavoc.org>
  29. #            Kenneth Christiansen <kenneth@gnu.org>
  30. #            Darin Adler <darin@bentspoon.com>
  31. #
  32. #  Proper XML UTF-8'ification written by Cyrille Chepelov <chepelov@calixo.net>
  33. #
  34.  
  35. ## Release information
  36. my $PROGRAM = "intltool-merge";
  37. my $PACKAGE = "intltool";
  38. my $VERSION = "0.40.4";
  39.  
  40. ## Loaded modules
  41. use strict; 
  42. use Getopt::Long;
  43. use Text::Wrap;
  44. use File::Basename;
  45.  
  46. my $must_end_tag      = -1;
  47. my $last_depth        = -1;
  48. my $translation_depth = -1;
  49. my @tag_stack = ();
  50. my @entered_tag = ();
  51. my @translation_strings = ();
  52. my $leading_space = "";
  53.  
  54. ## Scalars used by the option stuff
  55. my $HELP_ARG = 0;
  56. my $VERSION_ARG = 0;
  57. my $BA_STYLE_ARG = 0;
  58. my $XML_STYLE_ARG = 0;
  59. my $KEYS_STYLE_ARG = 0;
  60. my $DESKTOP_STYLE_ARG = 0;
  61. my $SCHEMAS_STYLE_ARG = 0;
  62. my $RFC822DEB_STYLE_ARG = 0;
  63. my $QUOTED_STYLE_ARG = 0;
  64. my $QUOTEDXML_STYLE_ARG = 0;
  65. my $QUIET_ARG = 0;
  66. my $PASS_THROUGH_ARG = 0;
  67. my $UTF8_ARG = 0;
  68. my $MULTIPLE_OUTPUT = 0;
  69. my $cache_file;
  70.  
  71. ## Handle options
  72. GetOptions 
  73. (
  74.  "help" => \$HELP_ARG,
  75.  "version" => \$VERSION_ARG,
  76.  "quiet|q" => \$QUIET_ARG,
  77.  "oaf-style|o" => \$BA_STYLE_ARG, ## for compatibility
  78.  "ba-style|b" => \$BA_STYLE_ARG,
  79.  "xml-style|x" => \$XML_STYLE_ARG,
  80.  "keys-style|k" => \$KEYS_STYLE_ARG,
  81.  "desktop-style|d" => \$DESKTOP_STYLE_ARG,
  82.  "schemas-style|s" => \$SCHEMAS_STYLE_ARG,
  83.  "rfc822deb-style|r" => \$RFC822DEB_STYLE_ARG,
  84.  "quoted-style" => \$QUOTED_STYLE_ARG,
  85.  "quotedxml-style" => \$QUOTEDXML_STYLE_ARG,
  86.  "pass-through|p" => \$PASS_THROUGH_ARG,
  87.  "utf8|u" => \$UTF8_ARG,
  88.  "multiple-output|m" => \$MULTIPLE_OUTPUT,
  89.  "cache|c=s" => \$cache_file
  90.  ) or &error;
  91.  
  92. my $PO_DIR;
  93. my $FILE;
  94. my $OUTFILE;
  95.  
  96. my %po_files_by_lang = ();
  97. my %translations = ();
  98. my $iconv = $ENV{"ICONV"} || "iconv";
  99. my $devnull = ($^O eq 'MSWin32' ? 'NUL:' : '/dev/null');
  100.  
  101. sub isProgramInPath
  102. {
  103.     my ($file) = @_;
  104.     # If either a file exists, or when run it returns 0 exit status
  105.     return 1 if ((-x $file) or (system("$file -l >$devnull") == 0));
  106.     return 0;
  107. }
  108.  
  109. if (! isProgramInPath ("$iconv"))
  110. {
  111.     print STDERR " *** iconv is not found on this system!\n".
  112.              " *** Without it, intltool-merge can not convert encodings.\n";
  113.     exit;
  114. }
  115.  
  116. # Use this instead of \w for XML files to handle more possible characters.
  117. my $w = "[-A-Za-z0-9._:]";
  118.  
  119. # XML quoted string contents
  120. my $q = "[^\\\"]*";
  121.  
  122. ## Check for options. 
  123.  
  124. if ($VERSION_ARG) 
  125. {
  126.     &print_version;
  127. elsif ($HELP_ARG) 
  128. {
  129.     &print_help;
  130. elsif ($BA_STYLE_ARG && @ARGV > 2) 
  131. {
  132.     &utf8_sanity_check;
  133.     &preparation;
  134.     &print_message;
  135.     &ba_merge_translations;
  136.     &finalize;
  137. elsif ($XML_STYLE_ARG && @ARGV > 2) 
  138. {
  139.     &utf8_sanity_check;
  140.     &preparation;
  141.     &print_message;
  142.     &xml_merge_output;
  143.     &finalize;
  144. elsif ($KEYS_STYLE_ARG && @ARGV > 2) 
  145. {
  146.     &utf8_sanity_check;
  147.     &preparation;
  148.     &print_message;
  149.         &keys_merge_translations;
  150.     &finalize;
  151. elsif ($DESKTOP_STYLE_ARG && @ARGV > 2) 
  152. {
  153.     &utf8_sanity_check;
  154.     &preparation;
  155.     &print_message;
  156.     &desktop_merge_translations;
  157.     &finalize;
  158. elsif ($SCHEMAS_STYLE_ARG && @ARGV > 2) 
  159. {
  160.     &utf8_sanity_check;
  161.     &preparation;
  162.     &print_message;
  163.     &schemas_merge_translations;
  164.     &finalize;
  165. elsif ($RFC822DEB_STYLE_ARG && @ARGV > 2) 
  166. {
  167.     &preparation;
  168.     &print_message;
  169.     &rfc822deb_merge_translations;
  170.     &finalize;
  171. elsif (($QUOTED_STYLE_ARG || $QUOTEDXML_STYLE_ARG) && @ARGV > 2)
  172. {
  173.     &utf8_sanity_check;
  174.     &preparation;
  175.     &print_message;
  176.     "ed_merge_translations($QUOTEDXML_STYLE_ARG);
  177.     &finalize;
  178. else 
  179. {
  180.     &print_help;
  181. }
  182.  
  183. exit;
  184.  
  185. ## Sub for printing release information
  186. sub print_version
  187. {
  188.     print <<_EOF_;
  189. ${PROGRAM} (${PACKAGE}) ${VERSION}
  190. Written by Maciej Stachowiak, Darin Adler and Kenneth Christiansen.
  191.  
  192. Copyright (C) 2000-2003 Free Software Foundation, Inc.
  193. Copyright (C) 2000-2001 Eazel, Inc.
  194. This is free software; see the source for copying conditions.  There is NO
  195. warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  196. _EOF_
  197.     exit;
  198. }
  199.  
  200. ## Sub for printing usage information
  201. sub print_help
  202. {
  203.     print <<_EOF_;
  204. Usage: ${PROGRAM} [OPTION]... PO_DIRECTORY FILENAME OUTPUT_FILE
  205. Generates an output file that includes some localized attributes from an
  206. untranslated source file.
  207.  
  208. Mandatory options: (exactly one must be specified)
  209.   -b, --ba-style         includes translations in the bonobo-activation style
  210.   -d, --desktop-style    includes translations in the desktop style
  211.   -k, --keys-style       includes translations in the keys style
  212.   -s, --schemas-style    includes translations in the schemas style
  213.   -r, --rfc822deb-style  includes translations in the RFC822 style
  214.       --quoted-style     includes translations in the quoted string style
  215.       --quotedxml-style  includes translations in the quoted xml string style
  216.   -x, --xml-style        includes translations in the standard xml style
  217.  
  218. Other options:
  219.   -u, --utf8             convert all strings to UTF-8 before merging 
  220.                          (default for everything except RFC822 style)
  221.   -p, --pass-through     deprecated, does nothing and issues a warning
  222.   -m, --multiple-output  output one localized file per locale, instead of 
  223.                      a single file containing all localized elements
  224.   -c, --cache=FILE       specify cache file name
  225.                          (usually \$top_builddir/po/.intltool-merge-cache)
  226.   -q, --quiet            suppress most messages
  227.       --help             display this help and exit
  228.       --version          output version information and exit
  229.  
  230. Report bugs to http://bugzilla.gnome.org/ (product name "$PACKAGE")
  231. or send email to <xml-i18n-tools\@gnome.org>.
  232. _EOF_
  233.     exit;
  234. }
  235.  
  236.  
  237. ## Sub for printing error messages
  238. sub print_error
  239. {
  240.     print STDERR "Try `${PROGRAM} --help' for more information.\n";
  241.     exit;
  242. }
  243.  
  244.  
  245. sub print_message 
  246. {
  247.     print "Merging translations into $OUTFILE.\n" unless $QUIET_ARG;
  248. }
  249.  
  250.  
  251. sub preparation 
  252. {
  253.     $PO_DIR = $ARGV[0];
  254.     $FILE = $ARGV[1];
  255.     $OUTFILE = $ARGV[2];
  256.  
  257.     &gather_po_files;
  258.     &get_translation_database;
  259. }
  260.  
  261. # General-purpose code for looking up translations in .po files
  262.  
  263. sub po_file2lang
  264. {
  265.     my ($tmp) = @_; 
  266.     $tmp =~ s/^.*\/(.*)\.po$/$1/; 
  267.     return $tmp; 
  268. }
  269.  
  270. sub gather_po_files
  271. {
  272.     if (my $linguas = $ENV{"LINGUAS"})
  273.     {
  274.         for my $lang (split / /, $linguas) {
  275.             my $po_file = $PO_DIR . "/" . $lang . ".po";
  276.             if (-e $po_file) {
  277.                 $po_files_by_lang{$lang} = $po_file;
  278.             }
  279.         }
  280.     }
  281.     else
  282.     {
  283.         if (open LINGUAS_FILE, "$PO_DIR/LINGUAS")
  284.         {
  285.             while (<LINGUAS_FILE>)
  286.             {
  287.                 next if /^#/;
  288.  
  289.                 for my $lang (split)
  290.                 {
  291.                     chomp ($lang);
  292.                     my $po_file = $PO_DIR . "/" . $lang . ".po";
  293.                     if (-e $po_file) {
  294.                         $po_files_by_lang{$lang} = $po_file;
  295.                     }
  296.                 }
  297.             }
  298.  
  299.             close LINGUAS_FILE;
  300.         }
  301.         else
  302.         {
  303.             for my $po_file (glob "$PO_DIR/*.po") {
  304.                 $po_files_by_lang{po_file2lang($po_file)} = $po_file;
  305.             }
  306.         }
  307.     }
  308. }
  309.  
  310. sub get_local_charset
  311. {
  312.     my ($encoding) = @_;
  313.     my $alias_file = $ENV{"G_CHARSET_ALIAS"} || "c:/devel/target/c515ef32b598e60d66a45e3c492ddef9/lib/charset.alias";
  314.  
  315.     # seek character encoding aliases in charset.alias (glib)
  316.  
  317.     if (open CHARSET_ALIAS, $alias_file) 
  318.     {
  319.     while (<CHARSET_ALIAS>) 
  320.         {
  321.             next if /^\#/;
  322.             return $1 if (/^\s*([-._a-zA-Z0-9]+)\s+$encoding\b/i)
  323.         }
  324.  
  325.         close CHARSET_ALIAS;
  326.     }
  327.  
  328.     # if not found, return input string
  329.  
  330.     return $encoding;
  331. }
  332.  
  333. sub get_po_encoding
  334. {
  335.     my ($in_po_file) = @_;
  336.     my $encoding = "";
  337.  
  338.     open IN_PO_FILE, $in_po_file or die;
  339.     while (<IN_PO_FILE>) 
  340.     {
  341.         ## example: "Content-Type: text/plain; charset=ISO-8859-1\n"
  342.         if (/Content-Type\:.*charset=([-a-zA-Z0-9]+)\\n/) 
  343.         {
  344.             $encoding = $1; 
  345.             last;
  346.         }
  347.     }
  348.     close IN_PO_FILE;
  349.  
  350.     if (!$encoding) 
  351.     {
  352.         print STDERR "Warning: no encoding found in $in_po_file. Assuming ISO-8859-1\n" unless $QUIET_ARG;
  353.         $encoding = "ISO-8859-1";
  354.     }
  355.  
  356.     system ("$iconv -f $encoding -t UTF-8 <$devnull 2>$devnull");
  357.     if ($?) {
  358.     $encoding = get_local_charset($encoding);
  359.     }
  360.  
  361.     return $encoding
  362. }
  363.  
  364. sub utf8_sanity_check 
  365. {
  366.     print STDERR "Warning: option --pass-through has been removed.\n" if $PASS_THROUGH_ARG;
  367.     $UTF8_ARG = 1;
  368. }
  369.  
  370. sub get_translation_database
  371. {
  372.     if ($cache_file) {
  373.     &get_cached_translation_database;
  374.     } else {
  375.         &create_translation_database;
  376.     }
  377. }
  378.  
  379. sub get_newest_po_age
  380. {
  381.     my $newest_age;
  382.  
  383.     foreach my $file (values %po_files_by_lang) 
  384.     {
  385.     my $file_age = -M $file;
  386.     $newest_age = $file_age if !$newest_age || $file_age < $newest_age;
  387.     }
  388.  
  389.     $newest_age = 0 if !$newest_age;
  390.  
  391.     return $newest_age;
  392. }
  393.  
  394. sub create_cache
  395. {
  396.     print "Generating and caching the translation database\n" unless $QUIET_ARG;
  397.  
  398.     &create_translation_database;
  399.  
  400.     open CACHE, ">$cache_file" || die;
  401.     print CACHE join "\x01", %translations;
  402.     close CACHE;
  403. }
  404.  
  405. sub load_cache 
  406. {
  407.     print "Found cached translation database\n" unless $QUIET_ARG;
  408.  
  409.     my $contents;
  410.     open CACHE, "<$cache_file" || die;
  411.     {
  412.         local $/;
  413.         $contents = <CACHE>;
  414.     }
  415.     close CACHE;
  416.     %translations = split "\x01", $contents;
  417. }
  418.  
  419. sub get_cached_translation_database
  420. {
  421.     my $cache_file_age = -M $cache_file;
  422.     if (defined $cache_file_age) 
  423.     {
  424.         if ($cache_file_age <= &get_newest_po_age) 
  425.         {
  426.             &load_cache;
  427.             return;
  428.         }
  429.         print "Found too-old cached translation database\n" unless $QUIET_ARG;
  430.     }
  431.  
  432.     &create_cache;
  433. }
  434.  
  435. sub add_translation
  436. {
  437.     my ($lang, $msgctxt, $msgid, $msgstr) = @_;
  438.  
  439.     return if !($msgid && $msgstr);
  440.  
  441.     if ($msgctxt) {
  442.     $msgid = "$msgctxt\004$msgid";
  443.     }
  444.     $translations{$lang, $msgid} = $msgstr;
  445. }
  446.  
  447. sub create_translation_database
  448. {
  449.     for my $lang (keys %po_files_by_lang) 
  450.     {
  451.         my $po_file = $po_files_by_lang{$lang};
  452.  
  453.         if ($UTF8_ARG) 
  454.         {
  455.             my $encoding = get_po_encoding ($po_file);
  456.  
  457.             if (lc $encoding eq "utf-8") 
  458.             {
  459.                 open PO_FILE, "<$po_file";    
  460.             } 
  461.             else 
  462.             {
  463.         print "NOTICE: $po_file is not in UTF-8 but $encoding, converting...\n" unless $QUIET_ARG;;
  464.  
  465.                 open PO_FILE, "$iconv -f $encoding -t UTF-8 $po_file|";    
  466.             }
  467.         } 
  468.         else 
  469.         {
  470.             open PO_FILE, "<$po_file";    
  471.         }
  472.  
  473.     my $nextfuzzy = 0;
  474.     my $inmsgctxt = 0;
  475.     my $inmsgid = 0;
  476.     my $inmsgstr = 0;
  477.     my $msgctxt = "";
  478.     my $msgid = "";
  479.     my $msgstr = "";
  480.  
  481.         while (<PO_FILE>) 
  482.         {
  483.         $nextfuzzy = 1 if /^#, fuzzy/;
  484.        
  485.         if (/^msgctxt "((\\.|[^\\]+)*)"/ ) 
  486.             {
  487.         if ($inmsgstr) {
  488.             add_translation ($lang, $msgctxt, $msgid, $msgstr);
  489.             $msgctxt = "";
  490.             $msgid = "";
  491.             $msgstr = "";
  492.         }
  493.  
  494.         $msgctxt = unescape_po_string($1);
  495.         $inmsgctxt = 1;
  496.         $inmsgid = 0;
  497.         $inmsgstr = 0;
  498.         }
  499.  
  500.         if (/^msgid "((\\.|[^\\]+)*)"/ ) 
  501.             {
  502.         if ($inmsgstr) {
  503.             add_translation ($lang, $msgctxt, $msgid, $msgstr);
  504.             $msgctxt = "";
  505.             $msgid = "";
  506.             $msgstr = "";
  507.         }
  508.  
  509.         if ($nextfuzzy) {
  510.             $inmsgid = 0;
  511.             $nextfuzzy = 0;
  512.         } else {
  513.             $msgid = unescape_po_string($1);
  514.             $inmsgid = 1;
  515.         }
  516.         $inmsgctxt = 0;
  517.         $inmsgstr = 0;
  518.         }
  519.  
  520.         if (/^msgstr "((\\.|[^\\]+)*)"/) 
  521.             {
  522.             $msgstr = unescape_po_string($1);
  523.         $inmsgstr = 1;
  524.         $inmsgctxt = 0;
  525.         $inmsgid = 0;
  526.         }
  527.  
  528.         if (/^"((\\.|[^\\]+)*)"/) 
  529.             {
  530.             $msgctxt .= unescape_po_string($1) if $inmsgctxt;
  531.             $msgid .= unescape_po_string($1) if $inmsgid;
  532.             $msgstr .= unescape_po_string($1) if $inmsgstr;
  533.         }
  534.     }
  535.     $translations{$lang, $msgid} = $msgstr if $inmsgstr && $msgid && $msgstr;
  536.     }
  537. }
  538.  
  539. sub finalize
  540. {
  541. }
  542.  
  543. sub unescape_one_sequence
  544. {
  545.     my ($sequence) = @_;
  546.  
  547.     return "\\" if $sequence eq "\\\\";
  548.     return "\"" if $sequence eq "\\\"";
  549.     return "\n" if $sequence eq "\\n";
  550.     return "\r" if $sequence eq "\\r";
  551.     return "\t" if $sequence eq "\\t";
  552.     return "\b" if $sequence eq "\\b";
  553.     return "\f" if $sequence eq "\\f";
  554.     return "\a" if $sequence eq "\\a";
  555.     return chr(11) if $sequence eq "\\v"; # vertical tab, see ascii(7)
  556.  
  557.     return chr(hex($1)) if ($sequence =~ /\\x([0-9a-fA-F]{2})/);
  558.     return chr(oct($1)) if ($sequence =~ /\\([0-7]{3})/);
  559.  
  560.     # FIXME: Is \0 supported as well? Kenneth and Rodney don't want it, see bug #48489
  561.  
  562.     return $sequence;
  563. }
  564.  
  565. sub unescape_po_string
  566. {
  567.     my ($string) = @_;
  568.  
  569.     $string =~ s/(\\x[0-9a-fA-F]{2}|\\[0-7]{3}|\\.)/unescape_one_sequence($1)/eg;
  570.  
  571.     return $string;
  572. }
  573.  
  574. sub entity_decode
  575. {
  576.     local ($_) = @_;
  577.  
  578.     s/'/'/g; # '
  579.     s/"/"/g; # "
  580.     s/</</g;
  581.     s/>/>/g;
  582.     s/&/&/g;
  583.  
  584.     return $_;
  585. }
  586.  
  587. # entity_encode: (string)
  588. #
  589. # Encode the given string to XML format (encode '<' etc).
  590.  
  591. sub entity_encode
  592. {
  593.     my ($pre_encoded) = @_;
  594.  
  595.     my @list_of_chars = unpack ('C*', $pre_encoded);
  596.  
  597.     # with UTF-8 we only encode minimalistic
  598.     return join ('', map (&entity_encode_int_minimalist, @list_of_chars));
  599. }
  600.  
  601. sub entity_encode_int_minimalist
  602. {
  603.     return """ if $_ == 34;
  604.     return "&" if $_ == 38;
  605.     return "'" if $_ == 39;
  606.     return "<" if $_ == 60;
  607.     return ">" if $_ == 62;
  608.     return chr $_;
  609. }
  610.  
  611. sub entity_encoded_translation
  612. {
  613.     my ($lang, $string) = @_;
  614.  
  615.     my $translation = $translations{$lang, $string};
  616.     return $string if !$translation;
  617.     return entity_encode ($translation);
  618. }
  619.  
  620. ## XML (bonobo-activation specific) merge code
  621.  
  622. sub ba_merge_translations
  623. {
  624.     my $source;
  625.  
  626.     {
  627.        local $/; # slurp mode
  628.        open INPUT, "<$FILE" or die "can't open $FILE: $!";
  629.        $source = <INPUT>;
  630.        close INPUT;
  631.     }
  632.  
  633.     open OUTPUT, ">$OUTFILE" or die "can't open $OUTFILE: $!";
  634.     # Binmode so that selftest works ok if using a native Win32 Perl...
  635.     binmode (OUTPUT) if $^O eq 'MSWin32';
  636.  
  637.     while ($source =~ s|^(.*?)([ \t]*<\s*$w+\s+($w+\s*=\s*"$q"\s*)+/?>)([ \t]*\n)?||s) 
  638.     {
  639.         print OUTPUT $1;
  640.  
  641.         my $node = $2 . "\n";
  642.  
  643.         my @strings = ();
  644.         $_ = $node;
  645.     while (s/(\s)_($w+\s*=\s*"($q)")/$1$2/s) {
  646.              push @strings, entity_decode($3);
  647.         }
  648.     print OUTPUT;
  649.  
  650.     my %langs;
  651.     for my $string (@strings) 
  652.         {
  653.         for my $lang (keys %po_files_by_lang) 
  654.             {
  655.                 $langs{$lang} = 1 if $translations{$lang, $string};
  656.         }
  657.     }
  658.     
  659.     for my $lang (sort keys %langs) 
  660.         {
  661.         $_ = $node;
  662.         s/(\sname\s*=\s*)"($q)"/$1"$2-$lang"/s;
  663.         s/(\s)_($w+\s*=\s*")($q)"/$1 . $2 . entity_encoded_translation($lang, $3) . '"'/seg;
  664.         print OUTPUT;
  665.         }
  666.     }
  667.  
  668.     print OUTPUT $source;
  669.  
  670.     close OUTPUT;
  671. }
  672.  
  673.  
  674. ## XML (non-bonobo-activation) merge code
  675.  
  676.  
  677. # Process tag attributes
  678. #   Only parameter is a HASH containing attributes -> values mapping
  679. sub getAttributeString
  680. {
  681.     my $sub = shift;
  682.     my $do_translate = shift || 0;
  683.     my $language = shift || "";
  684.     my $result = "";
  685.     my $translate = shift;
  686.     foreach my $e (reverse(sort(keys %{ $sub }))) {
  687.     my $key    = $e;
  688.     my $string = $sub->{$e};
  689.     my $quote = '"';
  690.     
  691.     $string =~ s/^[\s]+//;
  692.     $string =~ s/[\s]+$//;
  693.     
  694.     if ($string =~ /^'.*'$/)
  695.     {
  696.         $quote = "'";
  697.     }
  698.     $string =~ s/^['"]//g;
  699.     $string =~ s/['"]$//g;
  700.  
  701.     if ($do_translate && $key =~ /^_/) {
  702.         $key =~ s|^_||g;
  703.         if ($language) {
  704.         # Handle translation
  705.         my $decode_string = entity_decode($string);
  706.         my $translation = $translations{$language, $decode_string};
  707.         if ($translation) {
  708.             $translation = entity_encode($translation);
  709.             $string = $translation;
  710.                 }
  711.                 $$translate = 2;
  712.             } else {
  713.                  $$translate = 2 if ($translate && (!$$translate)); # watch not to "overwrite" $translate
  714.             }
  715.     }
  716.     
  717.     $result .= " $key=$quote$string$quote";
  718.     }
  719.     return $result;
  720. }
  721.  
  722. # Returns a translatable string from XML node, it works on contents of every node in XML::Parser tree
  723. sub getXMLstring
  724. {
  725.     my $ref = shift;
  726.     my $spacepreserve = shift || 0;
  727.     my @list = @{ $ref };
  728.     my $result = "";
  729.  
  730.     my $count = scalar(@list);
  731.     my $attrs = $list[0];
  732.     my $index = 1;
  733.  
  734.     $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
  735.     $spacepreserve = 0 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?default["']?$/));
  736.  
  737.     while ($index < $count) {
  738.     my $type = $list[$index];
  739.     my $content = $list[$index+1];
  740.         if (! $type ) {
  741.         # We've got CDATA
  742.         if ($content) {
  743.         # lets strip the whitespace here, and *ONLY* here
  744.                 $content =~ s/\s+/ /gs if (!$spacepreserve);
  745.         $result .= $content;
  746.         }
  747.     } elsif ( "$type" ne "1" ) {
  748.         # We've got another element
  749.         $result .= "<$type";
  750.         $result .= getAttributeString(@{$content}[0], 0); # no nested translatable elements
  751.         if ($content) {
  752.         my $subresult = getXMLstring($content, $spacepreserve);
  753.         if ($subresult) {
  754.             $result .= ">".$subresult . "</$type>";
  755.         } else {
  756.             $result .= "/>";
  757.         }
  758.         } else {
  759.         $result .= "/>";
  760.         }
  761.     }
  762.     $index += 2;
  763.     }
  764.     return $result;
  765. }
  766.  
  767. # Translate list of nodes if necessary
  768. sub translate_subnodes
  769. {
  770.     my $fh = shift;
  771.     my $content = shift;
  772.     my $language = shift || "";
  773.     my $singlelang = shift || 0;
  774.     my $spacepreserve = shift || 0;
  775.  
  776.     my @nodes = @{ $content };
  777.  
  778.     my $count = scalar(@nodes);
  779.     my $index = 0;
  780.     while ($index < $count) {
  781.         my $type = $nodes[$index];
  782.         my $rest = $nodes[$index+1];
  783.         if ($singlelang) {
  784.             my $oldMO = $MULTIPLE_OUTPUT;
  785.             $MULTIPLE_OUTPUT = 1;
  786.             traverse($fh, $type, $rest, $language, $spacepreserve);
  787.             $MULTIPLE_OUTPUT = $oldMO;
  788.         } else {
  789.             traverse($fh, $type, $rest, $language, $spacepreserve);
  790.         }
  791.         $index += 2;
  792.     }
  793. }
  794.  
  795. sub isWellFormedXmlFragment
  796. {
  797.     my $ret = eval 'require XML::Parser';
  798.     if(!$ret) {
  799.         die "You must have XML::Parser installed to run $0\n\n";
  800.     } 
  801.  
  802.     my $fragment = shift;
  803.     return 0 if (!$fragment);
  804.  
  805.     $fragment = "<root>$fragment</root>";
  806.     my $xp = new XML::Parser(Style => 'Tree');
  807.     my $tree = 0;
  808.     eval { $tree = $xp->parse($fragment); };
  809.     return $tree;
  810. }
  811.  
  812. sub traverse
  813. {
  814.     my $fh = shift; 
  815.     my $nodename = shift;
  816.     my $content = shift;
  817.     my $language = shift || "";
  818.     my $spacepreserve = shift || 0;
  819.  
  820.     if (!$nodename) {
  821.     if ($content =~ /^[\s]*$/) {
  822.         $leading_space .= $content;
  823.     }
  824.     print $fh $content;
  825.     } else {
  826.     # element
  827.     my @all = @{ $content };
  828.     my $attrs = shift @all;
  829.     my $translate = 0;
  830.     my $outattr = getAttributeString($attrs, 1, $language, \$translate);
  831.  
  832.     if ($nodename =~ /^_/) {
  833.         $translate = 1;
  834.         $nodename =~ s/^_//;
  835.     }
  836.     my $lookup = '';
  837.  
  838.         $spacepreserve = 0 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?default["']?$/));
  839.         $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
  840.  
  841.     print $fh "<$nodename", $outattr;
  842.     if ($translate) {
  843.         $content = getXMLstring($content, $spacepreserve);
  844.             if (!$spacepreserve) {
  845.                 $content =~ s/^\s+//s;
  846.                 $content =~ s/\s+$//s;
  847.             }
  848.             if (exists $attrs->{"msgctxt"}) {
  849.                 my $context = entity_decode ($attrs->{"msgctxt"});
  850.                 $context =~ s/^["'](.*)["']/$1/;
  851.                 $lookup = "$context\004$content";
  852.             } else {
  853.                 $lookup = $content;
  854.             }
  855.  
  856.         if ($lookup || $translate == 2) {
  857.                 my $translation = $translations{$language, $lookup} if isWellFormedXmlFragment($translations{$language, $lookup});
  858.                 if ($MULTIPLE_OUTPUT && ($translation || $translate == 2)) {
  859.                     $translation = $content if (!$translation);
  860.                     print $fh " xml:lang=\"", $language, "\"" if $language;
  861.                     print $fh ">";
  862.                     if ($translate == 2) {
  863.                         translate_subnodes($fh, \@all, $language, 1, $spacepreserve);
  864.                     } else {
  865.                         print $fh $translation;
  866.                     }
  867.                     print $fh "</$nodename>";
  868.  
  869.                     return; # this means there will be no same translation with xml:lang="$language"...
  870.                             # if we want them both, just remove this "return"
  871.                 } else {
  872.                     print $fh ">";
  873.                     if ($translate == 2) {
  874.                         translate_subnodes($fh, \@all, $language, 1, $spacepreserve);
  875.                     } else {
  876.                         print $fh $content;
  877.                     }
  878.                     print $fh "</$nodename>";
  879.                 }
  880.         } else {
  881.         print $fh "/>";
  882.         }
  883.  
  884.         for my $lang (sort keys %po_files_by_lang) {
  885.                     if ($MULTIPLE_OUTPUT && $lang ne "$language") {
  886.                         next;
  887.                     }
  888.             if ($lang) {
  889.                         # Handle translation
  890.                         #
  891.                         my $translate = 0;
  892.                         my $localattrs = getAttributeString($attrs, 1, $lang, \$translate);
  893.                         my $translation = $translations{$lang, $lookup} if isWellFormedXmlFragment($translations{$lang, $lookup});
  894.                         if ($translate && !$translation) {
  895.                             $translation = $content;
  896.                         }
  897.  
  898.                         if ($translation || $translate) {
  899.                 print $fh "\n";
  900.                 $leading_space =~ s/.*\n//g;
  901.                 print $fh $leading_space;
  902.                  print $fh "<", $nodename, " xml:lang=\"", $lang, "\"", $localattrs, ">";
  903.                             if ($translate == 2) {
  904.                                translate_subnodes($fh, \@all, $lang, 1, $spacepreserve);
  905.                             } else {
  906.                                 print $fh $translation;
  907.                             }
  908.                             print $fh "</$nodename>";
  909.             }
  910.                     }
  911.         }
  912.  
  913.     } else {
  914.         my $count = scalar(@all);
  915.         if ($count > 0) {
  916.         print $fh ">";
  917.                 my $index = 0;
  918.                 while ($index < $count) {
  919.                     my $type = $all[$index];
  920.                     my $rest = $all[$index+1];
  921.                     traverse($fh, $type, $rest, $language, $spacepreserve);
  922.                     $index += 2;
  923.                 }
  924.         print $fh "</$nodename>";
  925.         } else {
  926.         print $fh "/>";
  927.         }
  928.     }
  929.     }
  930. }
  931.  
  932. sub intltool_tree_comment
  933. {
  934.     my $expat = shift;
  935.     my $data  = shift;
  936.     my $clist = $expat->{Curlist};
  937.     my $pos   = $#$clist;
  938.  
  939.     push @$clist, 1 => $data;
  940. }
  941.  
  942. sub intltool_tree_cdatastart
  943. {
  944.     my $expat    = shift;
  945.     my $clist = $expat->{Curlist};
  946.     my $pos   = $#$clist;
  947.  
  948.     push @$clist, 0 => $expat->original_string();
  949. }
  950.  
  951. sub intltool_tree_cdataend
  952. {
  953.     my $expat    = shift;
  954.     my $clist = $expat->{Curlist};
  955.     my $pos   = $#$clist;
  956.  
  957.     $clist->[$pos] .= $expat->original_string();
  958. }
  959.  
  960. sub intltool_tree_char
  961. {
  962.     my $expat = shift;
  963.     my $text  = shift;
  964.     my $clist = $expat->{Curlist};
  965.     my $pos   = $#$clist;
  966.  
  967.     # Use original_string so that we retain escaped entities
  968.     # in CDATA sections.
  969.     #
  970.     if ($pos > 0 and $clist->[$pos - 1] eq '0') {
  971.         $clist->[$pos] .= $expat->original_string();
  972.     } else {
  973.         push @$clist, 0 => $expat->original_string();
  974.     }
  975. }
  976.  
  977. sub intltool_tree_start
  978. {
  979.     my $expat    = shift;
  980.     my $tag      = shift;
  981.     my @origlist = ();
  982.  
  983.     # Use original_string so that we retain escaped entities
  984.     # in attribute values.  We must convert the string to an
  985.     # @origlist array to conform to the structure of the Tree
  986.     # Style.
  987.     #
  988.     my @original_array = split /\x/, $expat->original_string();
  989.     my $source         = $expat->original_string();
  990.  
  991.     # Remove leading tag.
  992.     #
  993.     $source =~ s|^\s*<\s*(\S+)||s;
  994.  
  995.     # Grab attribute key/value pairs and push onto @origlist array.
  996.     #
  997.     while ($source)
  998.     {
  999.        if ($source =~ /^\s*([\w:-]+)\s*[=]\s*["]/)
  1000.        {
  1001.            $source =~ s|^\s*([\w:-]+)\s*[=]\s*["]([^"]*)["]||s;
  1002.            push @origlist, $1;
  1003.            push @origlist, '"' . $2 . '"';
  1004.        }
  1005.        elsif ($source =~ /^\s*([\w:-]+)\s*[=]\s*[']/)
  1006.        {
  1007.            $source =~ s|^\s*([\w:-]+)\s*[=]\s*[']([^']*)[']||s;
  1008.            push @origlist, $1;
  1009.            push @origlist, "'" . $2 . "'";
  1010.        }
  1011.        else
  1012.        {
  1013.            last;
  1014.        }
  1015.     }
  1016.  
  1017.     my $ol = [ { @origlist } ];
  1018.  
  1019.     push @{ $expat->{Lists} }, $expat->{Curlist};
  1020.     push @{ $expat->{Curlist} }, $tag => $ol;
  1021.     $expat->{Curlist} = $ol;
  1022. }
  1023.  
  1024. sub readXml
  1025. {
  1026.     my $filename = shift || return;
  1027.     if(!-f $filename) {
  1028.         die "ERROR Cannot find filename: $filename\n";
  1029.     }
  1030.  
  1031.     my $ret = eval 'require XML::Parser';
  1032.     if(!$ret) {
  1033.         die "You must have XML::Parser installed to run $0\n\n";
  1034.     } 
  1035.     my $xp = new XML::Parser(Style => 'Tree');
  1036.     $xp->setHandlers(Char => \&intltool_tree_char);
  1037.     $xp->setHandlers(Start => \&intltool_tree_start);
  1038.     $xp->setHandlers(CdataStart => \&intltool_tree_cdatastart);
  1039.     $xp->setHandlers(CdataEnd => \&intltool_tree_cdataend);
  1040.     my $tree = $xp->parsefile($filename);
  1041.  
  1042. # <foo><head id="a">Hello <em>there</em></head><bar>Howdy<ref/></bar>do</foo>
  1043. # would be:
  1044. # [foo, [{}, head, [{id => "a"}, 0, "Hello ",  em, [{}, 0, "there"]], bar, [{},
  1045. # 0, "Howdy",  ref, [{}]], 0, "do" ] ]
  1046.  
  1047.     return $tree;
  1048. }
  1049.  
  1050. sub print_header
  1051. {
  1052.     my $infile = shift;
  1053.     my $fh = shift;
  1054.     my $source;
  1055.  
  1056.     if(!-f $infile) {
  1057.         die "ERROR Cannot find filename: $infile\n";
  1058.     }
  1059.  
  1060.     print $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n};
  1061.     {
  1062.         local $/;
  1063.         open DOCINPUT, "<${FILE}" or die;
  1064.         $source = <DOCINPUT>;
  1065.         close DOCINPUT;
  1066.     }
  1067.     if ($source =~ /(<!DOCTYPE.*\[.*\]\s*>)/s)
  1068.     {
  1069.         print $fh "$1\n";
  1070.     }
  1071.     elsif ($source =~ /(<!DOCTYPE[^>]*>)/s)
  1072.     {
  1073.         print $fh "$1\n";
  1074.     }
  1075. }
  1076.  
  1077. sub parseTree
  1078. {
  1079.     my $fh        = shift;
  1080.     my $ref       = shift;
  1081.     my $language  = shift || "";
  1082.  
  1083.     my $name = shift @{ $ref };
  1084.     my $cont = shift @{ $ref };
  1085.     
  1086.     while (!$name || "$name" eq "1") {
  1087.         $name = shift @{ $ref };
  1088.         $cont = shift @{ $ref };
  1089.     }
  1090.  
  1091.     my $spacepreserve = 0;
  1092.     my $attrs = @{$cont}[0];
  1093.     $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
  1094.  
  1095.     traverse($fh, $name, $cont, $language, $spacepreserve);
  1096. }
  1097.  
  1098. sub xml_merge_output
  1099. {
  1100.     my $source;
  1101.  
  1102.     if ($MULTIPLE_OUTPUT) {
  1103.         for my $lang (sort keys %po_files_by_lang) {
  1104.         if ( ! -d $lang ) {
  1105.             mkdir $lang or -d $lang or die "Cannot create subdirectory $lang: $!\n";
  1106.             }
  1107.             open OUTPUT, ">$lang/$OUTFILE" or die "Cannot open $lang/$OUTFILE: $!\n";
  1108.             binmode (OUTPUT) if $^O eq 'MSWin32';
  1109.             my $tree = readXml($FILE);
  1110.             print_header($FILE, \*OUTPUT);
  1111.             parseTree(\*OUTPUT, $tree, $lang);
  1112.             close OUTPUT;
  1113.             print "CREATED $lang/$OUTFILE\n" unless $QUIET_ARG;
  1114.         }
  1115.         if ( ! -d "C" ) {
  1116.             mkdir "C" or -d "C" or die "Cannot create subdirectory C: $!\n";
  1117.         }
  1118.         open OUTPUT, ">C/$OUTFILE" or die "Cannot open C/$OUTFILE: $!\n";
  1119.         binmode (OUTPUT) if $^O eq 'MSWin32';
  1120.         my $tree = readXml($FILE);
  1121.         print_header($FILE, \*OUTPUT);
  1122.         parseTree(\*OUTPUT, $tree);
  1123.         close OUTPUT;
  1124.         print "CREATED C/$OUTFILE\n" unless $QUIET_ARG;
  1125.     } else {
  1126.         open OUTPUT, ">$OUTFILE" or die "Cannot open $OUTFILE: $!\n";
  1127.         binmode (OUTPUT) if $^O eq 'MSWin32';
  1128.         my $tree = readXml($FILE);
  1129.         print_header($FILE, \*OUTPUT);
  1130.         parseTree(\*OUTPUT, $tree);
  1131.         close OUTPUT;
  1132.         print "CREATED $OUTFILE\n" unless $QUIET_ARG;
  1133.     }
  1134. }
  1135.  
  1136. sub keys_merge_translation
  1137. {
  1138.     my ($lang) = @_;
  1139.  
  1140.     if ( ! -d $lang && $MULTIPLE_OUTPUT)
  1141.     {
  1142.         mkdir $lang or -d $lang or die "Cannot create subdirectory $lang: $!\n";
  1143.     }
  1144.  
  1145.     open INPUT, "<${FILE}" or die "Cannot open ${FILE}: $!\n";
  1146.     open OUTPUT, ">$lang/$OUTFILE" or die "Cannot open $lang/$OUTFILE: $!\n";
  1147.     binmode (OUTPUT) if $^O eq 'MSWin32';
  1148.  
  1149.     while (<INPUT>)
  1150.     {
  1151.         if (s/^(\s*)_(\w+=(.*))/$1$2/)
  1152.         {
  1153.             my $string = $3;
  1154.  
  1155.             if (!$MULTIPLE_OUTPUT)
  1156.             {
  1157.                 print OUTPUT;
  1158.  
  1159.                 my $non_translated_line = $_;
  1160.  
  1161.                 for my $lang (sort keys %po_files_by_lang)
  1162.                 {
  1163.                     my $translation = $translations{$lang, $string};
  1164.                     next if !$translation;
  1165.  
  1166.                     $_ = $non_translated_line;
  1167.                     s/(\w+)=.*/[$lang]$1=$translation/;
  1168.                     print OUTPUT;
  1169.                 }
  1170.             }
  1171.             else
  1172.             {
  1173.                 my $non_translated_line = $_;
  1174.                 my $translation = $translations{$lang, $string};
  1175.                 $translation = $string if !$translation;
  1176.  
  1177.                 $_ = $non_translated_line;
  1178.                 s/(\w+)=.*/$1=$translation/;
  1179.                 print OUTPUT;
  1180.             }
  1181.         }
  1182.         else
  1183.         {
  1184.             print OUTPUT;
  1185.         }
  1186.     }
  1187.  
  1188.     close OUTPUT;
  1189.     close INPUT;
  1190.  
  1191.     print "CREATED $lang/$OUTFILE\n" unless $QUIET_ARG;
  1192. }
  1193.  
  1194. sub keys_merge_translations
  1195. {
  1196.     if ($MULTIPLE_OUTPUT)
  1197.     {
  1198.         for my $lang (sort keys %po_files_by_lang)
  1199.         {
  1200.             keys_merge_translation ($lang);
  1201.         }
  1202.         keys_merge_translation ("C");
  1203.     }
  1204.     else
  1205.     {
  1206.         keys_merge_translation (".");
  1207.     }
  1208. }
  1209.  
  1210. sub desktop_merge_translations
  1211. {
  1212.     open INPUT, "<${FILE}" or die;
  1213.     open OUTPUT, ">${OUTFILE}" or die;
  1214.     binmode (OUTPUT) if $^O eq 'MSWin32';
  1215.  
  1216.     while (<INPUT>) 
  1217.     {
  1218.         if (s/^(\s*)_([A-Za-z0-9\-]+=(.*))/$1$2/)  
  1219.         {
  1220.         my $string = $3;
  1221.  
  1222.             print OUTPUT;
  1223.  
  1224.         my $non_translated_line = $_;
  1225.  
  1226.             for my $lang (sort keys %po_files_by_lang) 
  1227.             {
  1228.                 my $translation = $translations{$lang, $string};
  1229.                 next if !$translation;
  1230.  
  1231.                 $_ = $non_translated_line;
  1232.                 s/(\w+)=.*/${1}[$lang]=$translation/;
  1233.                 print OUTPUT;
  1234.             }
  1235.     } 
  1236.         else 
  1237.         {
  1238.             print OUTPUT;
  1239.         }
  1240.     }
  1241.  
  1242.     close OUTPUT;
  1243.     close INPUT;
  1244. }
  1245.  
  1246. sub schemas_merge_translations
  1247. {
  1248.     my $source;
  1249.  
  1250.     {
  1251.        local $/; # slurp mode
  1252.        open INPUT, "<$FILE" or die "can't open $FILE: $!";
  1253.        $source = <INPUT>;
  1254.        close INPUT;
  1255.     }
  1256.  
  1257.     open OUTPUT, ">$OUTFILE" or die;
  1258.     binmode (OUTPUT) if $^O eq 'MSWin32';
  1259.  
  1260.     # FIXME: support attribute translations
  1261.  
  1262.     # Empty nodes never need translation, so unmark all of them.
  1263.     # For example, <_foo/> is just replaced by <foo/>.
  1264.     $source =~ s|<\s*_($w+)\s*/>|<$1/>|g;
  1265.  
  1266.     while ($source =~ s/
  1267.                         (.*?)
  1268.                         (\s+)(<locale\ name="C">(\s*)
  1269.                             (<default>\s*(?:<!--[^>]*?-->\s*)?(.*?)\s*<\/default>)?(\s*)
  1270.                             (<short>\s*(?:<!--[^>]*?-->\s*)?(.*?)\s*<\/short>)?(\s*)
  1271.                             (<long>\s*(?:<!--[^>]*?-->\s*)?(.*?)\s*<\/long>)?(\s*)
  1272.                         <\/locale>)
  1273.                        //sx) 
  1274.     {
  1275.         print OUTPUT $1;
  1276.  
  1277.     my $locale_start_spaces = $2 ? $2 : '';
  1278.     my $default_spaces = $4 ? $4 : '';
  1279.     my $short_spaces = $7 ? $7 : '';
  1280.     my $long_spaces = $10 ? $10 : '';
  1281.     my $locale_end_spaces = $13 ? $13 : '';
  1282.     my $c_default_block = $3 ? $3 : '';
  1283.     my $default_string = $6 ? $6 : '';
  1284.     my $short_string = $9 ? $9 : '';
  1285.     my $long_string = $12 ? $12 : '';
  1286.  
  1287.     print OUTPUT "$locale_start_spaces$c_default_block";
  1288.  
  1289.         $default_string =~ s/\s+/ /g;
  1290.         $default_string = entity_decode($default_string);
  1291.     $short_string =~ s/\s+/ /g;
  1292.     $short_string = entity_decode($short_string);
  1293.     $long_string =~ s/\s+/ /g;
  1294.     $long_string = entity_decode($long_string);
  1295.  
  1296.     for my $lang (sort keys %po_files_by_lang) 
  1297.         {
  1298.         my $default_translation = $translations{$lang, $default_string};
  1299.         my $short_translation = $translations{$lang, $short_string};
  1300.         my $long_translation  = $translations{$lang, $long_string};
  1301.  
  1302.         next if (!$default_translation && !$short_translation && 
  1303.                      !$long_translation);
  1304.  
  1305.         print OUTPUT "\n$locale_start_spaces<locale name=\"$lang\">";
  1306.  
  1307.         print OUTPUT "$default_spaces";    
  1308.  
  1309.         if ($default_translation)
  1310.         {
  1311.             $default_translation = entity_encode($default_translation);
  1312.             print OUTPUT "<default>$default_translation</default>";
  1313.         }
  1314.  
  1315.         print OUTPUT "$short_spaces";
  1316.  
  1317.         if ($short_translation)
  1318.         {
  1319.             $short_translation = entity_encode($short_translation);
  1320.             print OUTPUT "<short>$short_translation</short>";
  1321.         }
  1322.  
  1323.         print OUTPUT "$long_spaces";
  1324.  
  1325.         if ($long_translation)
  1326.         {
  1327.             $long_translation = entity_encode($long_translation);
  1328.             print OUTPUT "<long>$long_translation</long>";
  1329.         }        
  1330.  
  1331.         print OUTPUT "$locale_end_spaces</locale>";
  1332.         }
  1333.     }
  1334.  
  1335.     print OUTPUT $source;
  1336.  
  1337.     close OUTPUT;
  1338. }
  1339.  
  1340. sub rfc822deb_merge_translations
  1341. {
  1342.     my %encodings = ();
  1343.     for my $lang (keys %po_files_by_lang) {
  1344.         $encodings{$lang} = ($UTF8_ARG ? 'UTF-8' : get_po_encoding($po_files_by_lang{$lang}));
  1345.     }
  1346.  
  1347.     my $source;
  1348.  
  1349.     $Text::Wrap::huge = 'overflow';
  1350.     $Text::Wrap::break = qr/\n|\s(?=\S)/;
  1351.  
  1352.     {
  1353.        local $/; # slurp mode
  1354.        open INPUT, "<$FILE" or die "can't open $FILE: $!";
  1355.        $source = <INPUT>;
  1356.        close INPUT;
  1357.     }
  1358.  
  1359.     open OUTPUT, ">${OUTFILE}" or die;
  1360.     binmode (OUTPUT) if $^O eq 'MSWin32';
  1361.  
  1362.     while ($source =~ /(^|\n+)(_*)([^:\s]+)(:[ \t]*)(.*?)(?=\n[\S\n]|$)/sg)
  1363.     {
  1364.         my $sep = $1;
  1365.         my $non_translated_line = $3.$4;
  1366.         my $string = $5;
  1367.         my $underscore = length($2);
  1368.         next if $underscore eq 0 && $non_translated_line =~ /^#/;
  1369.         #  Remove [] dummy strings
  1370.         my $stripped = $string;
  1371.         $stripped =~ s/\[\s[^\[\]]*\],/,/g if $underscore eq 2;
  1372.         $stripped =~ s/\[\s[^\[\]]*\]$//;
  1373.         $non_translated_line .= $stripped;
  1374.  
  1375.         print OUTPUT $sep.$non_translated_line;
  1376.     
  1377.         if ($underscore) 
  1378.         {
  1379.             my @str_list = rfc822deb_split($underscore, $string);
  1380.  
  1381.             for my $lang (sort keys %po_files_by_lang) 
  1382.                 {
  1383.                     my $is_translated = 1;
  1384.                     my $str_translated = '';
  1385.                     my $first = 1;
  1386.                 
  1387.                     for my $str (@str_list) 
  1388.                     {
  1389.                         my $translation = $translations{$lang, $str};
  1390.                     
  1391.                         if (!$translation) 
  1392.                         {
  1393.                             $is_translated = 0;
  1394.                             last;
  1395.                         }
  1396.  
  1397.                     #  $translation may also contain [] dummy
  1398.                         #  strings, mostly to indicate an empty string
  1399.                     $translation =~ s/\[\s[^\[\]]*\]$//;
  1400.                         
  1401.                         if ($first) 
  1402.                         {
  1403.                             if ($underscore eq 2)
  1404.                             {
  1405.                                 $str_translated .= $translation;
  1406.                             }
  1407.                             else
  1408.                             {
  1409.                                 $str_translated .=
  1410.                                     Text::Tabs::expand($translation) .
  1411.                                     "\n";
  1412.                             }
  1413.                         } 
  1414.                         else 
  1415.                         {
  1416.                             if ($underscore eq 2)
  1417.                             {
  1418.                                 $str_translated .= ', ' . $translation;
  1419.                             }
  1420.                             else
  1421.                             {
  1422.                                 $str_translated .= Text::Tabs::expand(
  1423.                                     Text::Wrap::wrap(' ', ' ', $translation)) .
  1424.                                     "\n .\n";
  1425.                             }
  1426.                         }
  1427.                         $first = 0;
  1428.  
  1429.                         #  To fix some problems with Text::Wrap::wrap
  1430.                         $str_translated =~ s/(\n )+\n/\n .\n/g;
  1431.                     }
  1432.                     next unless $is_translated;
  1433.  
  1434.                     $str_translated =~ s/\n \.\n$//;
  1435.                     $str_translated =~ s/\s+$//;
  1436.  
  1437.                     $_ = $non_translated_line;
  1438.                     s/^(\w+):\s*.*/$sep${1}-$lang.$encodings{$lang}: $str_translated/s;
  1439.                     print OUTPUT;
  1440.                 }
  1441.         }
  1442.     }
  1443.     print OUTPUT "\n";
  1444.  
  1445.     close OUTPUT;
  1446.     close INPUT;
  1447. }
  1448.  
  1449. sub rfc822deb_split 
  1450. {
  1451.     # Debian defines a special way to deal with rfc822-style files:
  1452.     # when a value contain newlines, it consists of
  1453.     #   1.  a short form (first line)
  1454.     #   2.  a long description, all lines begin with a space,
  1455.     #       and paragraphs are separated by a single dot on a line
  1456.     # This routine returns an array of all paragraphs, and reformat
  1457.     # them.
  1458.     # When first argument is 2, the string is a comma separated list of
  1459.     # values.
  1460.     my $type = shift;
  1461.     my $text = shift;
  1462.     $text =~ s/^[ \t]//mg;
  1463.     return (split(/, */, $text, 0)) if $type ne 1;
  1464.     return ($text) if $text !~ /\n/;
  1465.  
  1466.     $text =~ s/([^\n]*)\n//;
  1467.     my @list = ($1);
  1468.     my $str = '';
  1469.  
  1470.     for my $line (split (/\n/, $text)) 
  1471.     {
  1472.         chomp $line;
  1473.         if ($line =~ /^\.\s*$/)
  1474.         {
  1475.             #  New paragraph
  1476.             $str =~ s/\s*$//;
  1477.             push(@list, $str);
  1478.             $str = '';
  1479.         } 
  1480.         elsif ($line =~ /^\s/) 
  1481.         {
  1482.             #  Line which must not be reformatted
  1483.             $str .= "\n" if length ($str) && $str !~ /\n$/;
  1484.             $line =~ s/\s+$//;
  1485.             $str .= $line."\n";
  1486.         } 
  1487.         else 
  1488.         {
  1489.             #  Continuation line, remove newline
  1490.             $str .= " " if length ($str) && $str !~ /\n$/;
  1491.             $str .= $line;
  1492.         }
  1493.     }
  1494.  
  1495.     $str =~ s/\s*$//;
  1496.     push(@list, $str) if length ($str);
  1497.  
  1498.     return @list;
  1499. }
  1500.  
  1501. sub quoted_translation
  1502. {
  1503.     my ($xml_mode, $lang, $string) = @_;
  1504.  
  1505.     $string = entity_decode($string) if $xml_mode;
  1506.     $string =~ s/\\\"/\"/g;
  1507.  
  1508.     my $translation = $translations{$lang, $string};
  1509.     $translation = $string if !$translation;
  1510.     $translation = entity_encode($translation) if $xml_mode;
  1511.     $translation =~ s/\"/\\\"/g;
  1512.     return $translation
  1513. }
  1514.  
  1515. sub quoted_merge_translations
  1516. {
  1517.     my ($xml_mode) = @_;
  1518.  
  1519.     if (!$MULTIPLE_OUTPUT) {
  1520.         print "Quoted only supports Multiple Output.\n";
  1521.         exit(1);
  1522.     }
  1523.  
  1524.     for my $lang (sort keys %po_files_by_lang) {
  1525.         if ( ! -d $lang ) {
  1526.             mkdir $lang or -d $lang or die "Cannot create subdirectory $lang: $!\n";
  1527.         }
  1528.         open INPUT, "<${FILE}" or die;
  1529.         open OUTPUT, ">$lang/$OUTFILE" or die "Cannot open $lang/$OUTFILE: $!\n";
  1530.         binmode (OUTPUT) if $^O eq 'MSWin32';
  1531.         while (<INPUT>) 
  1532.         {
  1533.             s/\"(([^\"]|\\\")*[^\\\"])\"/"\"" . "ed_translation($xml_mode, $lang, $1) . "\""/ge;
  1534.             print OUTPUT;
  1535.         }
  1536.         close OUTPUT;
  1537.         close INPUT;
  1538.     }
  1539. }
  1540.